package xhttp

import (
	"errors"
	"net/http"
	"runtime"
	"strings"

	"gitee.com/lihaiping1603/utils"
	"gitee.com/lihaiping1603/utils/log"
)

var (
	ErrNotMatchPath = errors.New("not match request path")
)

type HttpServerMux struct {
	*http.ServeMux
	root *node //
}

func NewHttpServerMux() *HttpServerMux {
	return &HttpServerMux{ServeMux: http.NewServeMux(), root: &node{}}
}

// DefaultServeMux is the default ServeMux used by Serve.
//var DefaultServeMux = &HttpServerMux{ServeMux: http.NewServeMux(), root: &node{}}

func (mux *HttpServerMux) RegisterHandler(pattern string, method string, handler http.HandlerFunc) error {
	if "" == pattern {
		log.Error("the pattern is empty")
		return errors.New("the pattern is empty")
	}
	if method == "" {
		mux.addHandler(pattern, handlePanic(handler))
	} else {
		//请求来了以后,执行顺序依次为handlePanic->requireMethod->handler
		mux.addHandler(pattern, handlePanic(requireMethod(method, handler)))
	}
	return nil
}

func (mux *HttpServerMux) RegisterSessionHandler(pattern string, method string, handler SessionHandlerFunc) error {
	return mux.RegisterHandler(pattern, method, func(writer http.ResponseWriter, request *http.Request) {
		session := NewSession(writer, request)
		handler(session)
	})
}

func (mux *HttpServerMux) addHandler(pattern string, handler http.HandlerFunc) {
	parts := parsePattern(pattern)
	mux.root.insert(pattern, parts, 0, handler)
}

func (mux *HttpServerMux) getHandler(path string) (http.HandlerFunc, error) {
	searchParts := parsePattern(path)
	n := mux.root.search(searchParts, 0)
	if n != nil {
		return n.handler, nil
	}
	log.Error("not match request path:%s", path)
	return nil, ErrNotMatchPath
}

func (mux *HttpServerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	hander, err := mux.getHandler(path)
	if err != nil {
		http.NotFoundHandler().ServeHTTP(w, r)
		return
	}
	hander.ServeHTTP(w, r)
}

// Only one * is allowed
func parsePattern(pattern string) []string {
	vs := strings.Split(pattern, "/")

	parts := make([]string, 0)
	for _, item := range vs {
		if item != "" {
			parts = append(parts, item)
			if item[0] == '*' {
				break
			}
		}
	}
	return parts
}

// requireMethod returns an xhttp.HandlerFunc that checks whether
// the method of a client request matches the expected method before
// calling f.
//
// If the client request method does not match the given method
// it returns an error and xhttp.StatusMethodNotAllowed to the client.
func requireMethod(method string, f http.HandlerFunc) http.HandlerFunc {
	var ErrMethodNotAllowed = NewError(http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed))

	return func(w http.ResponseWriter, r *http.Request) {
		if method != r.Method {
			w.Header().Set("Accept", method)
			sendHttpResError(w, ErrMethodNotAllowed)
			return
		}
		f(w, r)
	}
}

func handlePanic(f http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func() {
			err := recover()
			if err != nil {
				switch err.(type) {
				case runtime.Error: // 运行时错误
					log.Error("runtime error: %s", err)
				default: // 非运行时错误
					log.Error("error: %s", err)
				}
				utils.PanicTrace()
			}
		}()
		f(writer, request)
	}
}
